/*
 * Clase que inicia un servidor para comenzar un chat
 * CLASE SUSTRAIDA DE UNA PAGINA WEB
 */
package servclien;

import listaSimpleServer.SLL;
import java.io.*;
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.*;

public class Server {
    // ID de una conexion unica
    private static int _uniqueId;
    // Lista que mantiene los clientes conectados al chat
    private SLL _listclient;
    // Interfaz grafica del servidor
    private ServerGUI _sg;
    // Tiempo actual
    private SimpleDateFormat _sdf;
    // Numero de puerto para la conexion
    private int _port;
    // Apaga el chat cuando se torna falso
    private boolean _keepGoing;
    
    /**
     * Constructor, recibe como parametro el puerto para la conexion
     * y no muestra interfaz grafica.
     * @param port Numero de puerto para conectarse
     */
    public Server(int pPort) {
        this(pPort, null);
    }
	
    /**
     * Constructor, recibe el puerto para la conexion y una interfaz
     * para el servidor.
     * @param pPort Numero de puerto para iniciar la conexion
     * @param pSg Interfaz grafica para el servidor
     */
    public Server(int pPort, ServerGUI pSg) {
	_sg = pSg;
	_port = pPort;
	_sdf = new SimpleDateFormat("HH:mm:ss");
	_listclient = new SLL();
    }
	
    /**
     * Inicia el hilo de la ejecucion del servidor
     */
    public void start() {
	_keepGoing = true;
	try {
            ServerSocket serverSocket = new ServerSocket(_port);
            while(_keepGoing) {
                display("Server waiting for Clients on port " + _port + ".");
				
		Socket socket = serverSocket.accept();
                if(!_keepGoing)
                    break;
                ClientThread t = new ClientThread(socket);  // make a thread of it
		_listclient.meterFinal(t);									// save it in the ArrayList
		t.start();
            }
	    try {
                serverSocket.close();
		for(int i = 1; i <= _listclient.getLength(); ++i) {
                    ClientThread tc = _listclient.obtenerPorPosicion(i);
		    try {
                        tc._sInput.close();
			tc._sOutput.close();
			tc._socket.close();
		    }
		    catch(IOException pIoE) { }
                }
	    }
            catch(Exception pE) {
                display("Exception closing the server and clients: " + pE);
	    }
	}
	catch (IOException pE) {
            String msg = _sdf.format(new Date()) + " Exception on new ServerSocket: " + pE + "\n";
	    display(msg);
	}
    }
    
    /**
     * Detiene el servidor de la GUI
     */
    protected void stop() {
        _keepGoing = false;
	
	try {
            Socket socket = new Socket("localhost", _port);
	}
	catch(Exception pE) { }
    }
    
    /**
     * Muestra un evento a la consola o a la GUI.
     * @param pMsg Mensaje a mostrar
     */
    private void display(String pMsg) {
        String time = _sdf.format(new Date()) + " " + pMsg;
	if(_sg == null){
	    System.out.println(time);
        }
        else
            System.out.println(time);
                        
    }
    
    /**
     * Publica el mensaje a todos los clientes
     * @param pMessage Mensaje a publicar
     */
    private synchronized void broadcast(String pMessage) {
        String time = _sdf.format(new Date());
	String messageLf = time + " " + pMessage + "\n";
	if(_sg == null)
            System.out.print(messageLf);
        else
            _sg.appendRoom(messageLf);
                
        for(int i=1; _listclient.getLength()>=i;i++ ) {
            ClientThread ct = _listclient.obtenerPorPosicion(i);
                   
	    if(!ct.writeMsg(messageLf)) {
                _listclient.eliminaPorPosicion(i); 
                display("Disconnected Client " + ct._username + " removed from list.");
            }
        }
    }
    
    /**
     * Para un cliente que cierre sesion en el chat.
     * @param pId ID del cliente para remover de la lista clientes
     */
    synchronized void remove(int pId) {
	for(int i = 1; i <= _listclient.getLength(); ++i) {
            ClientThread ct = _listclient.obtenerPorPosicion(i);
	    if(ct._id == pId) {
                _listclient.eliminaPorPosicion(i);
		return;
	    }
        }
    }

    public static void main(String[] pArgs) {
        int portNumber = 1500;
	switch(pArgs.length) {
	case 1:
            try {
                portNumber = Integer.parseInt(pArgs[0]);
	    }
	    catch(Exception pE) {
                System.out.println("Invalid port number.");
		System.out.println("Usage is: > java Server [portNumber]");
		return;
	    }
	    case 0:
                break;
	    default:
                System.out.println("Usage is: > java Server [portNumber]");
	        return;
	}
	Server server = new Server(portNumber);
	server.start();
    }

    /**
     * Una instancia de hilo que corre para cada cliente
     */
    public class ClientThread extends Thread {
        Socket _socket;
	ObjectInputStream _sInput;
        ObjectOutputStream _sOutput;
	int _id;
	String _username;
	ChatMessage _cm;
	String _date;
            
        ClientThread(Socket pSocket) {
            _id = ++_uniqueId;
	    _socket = pSocket;
	    System.out.println("Thread trying to create Object Input/Output Streams");
	    try {
                _sOutput = new ObjectOutputStream(pSocket.getOutputStream());
		_sInput  = new ObjectInputStream(pSocket.getInputStream());
				
		_username = (String) _sInput.readObject();
		display(_username + " just connected.");
                broadcast(_username + " just connected.");
	    }
	    catch (IOException pE) {
                display("Exception creating new Input/output Streams: " + pE);
	        return;
	    }
	    catch (ClassNotFoundException pE) { }
            _date = new Date().toString() + "\n";
        }

        @Override
	public void run() {
            boolean keepGoing = true;
	    while(keepGoing) {
                try {
                    _cm = (ChatMessage) _sInput.readObject();
		}
		catch (IOException pE) {
                    display(_username + " Exception reading Streams: " + pE);
		    break;				
		}
		catch(ClassNotFoundException pE2) {
                    break;
		}
		String message = _cm.getMessage();
                        
		switch(_cm.getType()) {
                    case ChatMessage._messageNumber:
                    broadcast(_username + ": " + message);
		    break;
		    case ChatMessage.LOGOUT:
                        display(_username + " disconnected with a LOGOUT message.");
                        broadcast(_username + " have disconnected.");
			keepGoing = false;
			break;
		    case ChatMessage._whoIsIn:
                        writeMsg("List of the users connected at " + _sdf.format(new Date()) + "\n");
			for(int i = 1; i <= _listclient.getLength(); ++i) {
                            ClientThread ct = _listclient.obtenerPorPosicion(i);
			    writeMsg((i) + ") " + ct._username + " since " + ct._date);
			}
			break;
		}
	    }   
			
	    remove(_id);
	    close();
	}
		
        @SuppressWarnings("empty-statement")
        private void close() {
            try {
                if(_sOutput != null) _sOutput.close();
            }
	    catch(Exception pE) { }
            try {
                if(_sInput != null) _sInput.close();
	    }
	    catch(Exception pE) {};
		    try {
                        if(_socket != null) _socket.close();
		    }
		    catch (Exception pE) {}
	}

	private boolean writeMsg(String pMsg) {
            if(!_socket.isConnected()) {
                close();
                return false;
            }
			
            try {
                _sOutput.writeObject(pMsg);
            }
			
            catch(IOException pE) {
                display("Error sending message to " + _username);
		display(pE.toString());
            }
	    return true;
	}
    }
}


